package nl.grol.yakshop.web.servlet.json;

import java.io.Reader;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import nl.grol.yakshop.domain.model.CustomerOrder;
import nl.grol.yakshop.domain.model.ProcessedOrder;
import nl.grol.yakshop.domain.model.YakI;
import nl.grol.yakshop.util.InvalidInputException;

import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public final class JsonConverter {
    private static final Logger LOGGER = Logger.getLogger(JsonConverter.class.getName());

    private JsonConverter() {
    }
    
	public static CustomerOrder readCustomerOrderFromJson( int day, Reader reader ) throws InvalidInputException {
		CustomerOrder order = null;

		try {
	        Gson gson = new GsonBuilder().create();
	        nl.grol.yakshop.web.servlet.json.CustomerOrder externalCustomerOrder = 
	        	gson.fromJson( reader, 
	        			nl.grol.yakshop.web.servlet.json.CustomerOrder.class );
	        if( externalCustomerOrder == null ) {
	            LOGGER.warning("Error parsing customer-order from json" );
	        } else {
	        	if( externalCustomerOrder.isValid() == false ) {
	        		
	                LOGGER.warning("Invalid customer order received" );
	        		
	        	} else {
		        	order = new CustomerOrder( day, externalCustomerOrder.getCustomer(), 
		        						externalCustomerOrder.getOrder().getMilk(),
		        						externalCustomerOrder.getOrder().getSkins() );
	        	}
	        }
		} catch( Exception exc ) {
			
			LOGGER.warning( exc.getMessage() );
			
			throw new InvalidInputException("Invalid json:" + exc.getMessage());
		}
        
        return order;
		
	}

	public static String makeJsonStock( Double milk, Integer skins) {
		Stock stock = new Stock(milk, skins);
		
		return JsonConverter.gsonInstance().toJson(stock);
	}
	
	public static String makeJsonProcessedOrder( ProcessedOrder processedOrder ) {
		
		Stock stock = new Stock(processedOrder.getMilkInLiters(), 
								processedOrder.getSkins() );
		
		return JsonConverter.gsonInstance().toJson(stock);
	}
	
	public static String makeJsonProcessedOrders( List<ProcessedOrder> processedOrders ) {
		List<nl.grol.yakshop.web.servlet.json.ProcessedOrder> externalOrders = 
			new ArrayList<nl.grol.yakshop.web.servlet.json.ProcessedOrder>();
		
		for( ProcessedOrder order : processedOrders) {

			externalOrders.add( 
					new nl.grol.yakshop.web.servlet.json.ProcessedOrder( 
							order.getForDay(),
							order.getCustomerName(),
							new Stock(order.getMilkInLiters(), order.getSkins() )
					) 
			);
		}
		ProcessedOrders orderList = new ProcessedOrders(externalOrders);
		return JsonConverter.gsonInstance().toJson(orderList);
	}
	
	public static String makeJsonHerd(int dayOffsetSinceShopOpening, ImmutableList<YakI> yaks) {
		List<Yak> externalYaks = new ArrayList<Yak>();
		for( YakI yak : yaks) {
			if( yak.isAliveOnDay(dayOffsetSinceShopOpening) == true ) {
				/*
				 * Only yaks that are alive are returned
				 */
				externalYaks.add( new Yak( 
								yak.getName(), 
								yak.getAgeInYearsOnDay(dayOffsetSinceShopOpening),
								yak.getLastShavedAgeInYearsOnDay(dayOffsetSinceShopOpening)) );
			}
		}
		Herd externalHerd = new Herd( externalYaks );
		
		return JsonConverter.gsonInstance().toJson(externalHerd);
	}
	
	private  static Gson gsonInstance() {
	    return  new GsonBuilder() 
	    	.setPrettyPrinting()
		    .registerTypeAdapter(double.class, new JsonSerializer<Double>()  { 
		    	/*
		    	 * Make sure the doubles are formatted nicely
		    	 */
               public JsonElement serialize(Double value, Type theType, 
            		   	JsonSerializationContext context) { 
                       if (value.isNaN()) { 
                           return new JsonPrimitive(0); // Convert NaN to zero 
                       } else if (value.isInfinite() ) { 
                           return new JsonPrimitive(value); // Leave infinite alone 
                       } else { 
                           // Keep 1 decimal digits only 
                           return new JsonPrimitive((new BigDecimal(value)).setScale(1, 
                            		   	BigDecimal.ROUND_HALF_UP)); 
                       } 
               } 
	       }).create(); 
	}
}
